﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Transactions;
using Security;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;

namespace Groupware
{
    /// <summary>
    /// Rappresents a notification to send to user
    /// </summary>
    public class Notification
    {
        #region Static Fields

        /*
         * Static constants indicating notification type
         */
        public const byte TRIP_DELETED = 1;
        public const byte CONFIRM_RESERVATION = 2;
        public const byte DENIED_RESERVATION = 3;
        public const byte NOTIFY_FELLOWSHIP = 4;

        public const byte EVENT_MODIFIED = 5;
        public const byte EVENT_DELETED = 6;
        public const byte CREATED_TRIP_FOR_EVENT = 7;

        public const byte FRIEND_REQUEST = 8;
        public const byte FRIEND_REQUEST_ASSOCIATED_TRIP = 9;
        public const byte PASSENGER_DELETE = 10;
        public const byte OWNER_DELETE = 11;

        public const byte TRIP_CREATED = 12;
        public const byte TRIP_MODIFIED = 13;

        public const byte RESERVATION_REQ = 14;

        public const byte FRIEND_REQUEST_ACCEPTED = 128;

        #endregion

        #region Fields

        /// <summary>
        /// Notification id
        /// </summary>
        private int id;

        /// <summary>
        /// Contains notification fields
        /// </summary>
        private Hashtable fields;

        /// <summary>
        /// Trip associated to the notification, if existing
        /// </summary>
        private Nullable<int> tripId;

        /// <summary>
        /// Entities which could be associated to the notification
        /// </summary>
        private Nullable<int> eventId;


        /// <summary>
        /// notification in db
        /// </summary>
        private Storage.EntityModels.Notifications dbNotification;

        /// <summary>
        /// Template name and Template Type (integer format: view costants in region "Static Field")
        /// </summary>
        private byte templateType;

        /// <summary>
        /// SenderID identifies the user who generated the notification
        /// </summary>
        private Nullable<int> senderID;

        /// <summary>
        /// check if notification is read
        /// </summary>
        private bool isRead;
        
        #endregion

        #region Properties

        public int Id
        {
            get { return id; }
            internal set { id = value; }
        }

        public Hashtable Fields
        {
            get { return fields; }
            internal set { fields = value; }
        }

        public Nullable<int> UserId
        {
            get { return dbNotification.UserId; }
        }

        public Nullable<int> TripId
        {
            get { return tripId; }
            set { tripId = value; }
        }

        public Nullable<int> EventId
        {
            get { return eventId; }
            set { eventId = value; }
        }

        public byte TemplateType
        {
            get { return this.templateType; }
            set { this.templateType = value; }
        }

        public Nullable<int> SenderID
        {
            get { return this.senderID; }
            set { this.senderID = value; }
        }

        public bool IsRead
        {
            get { return this.isRead; }
            internal set { isRead = value; }
        }

        #endregion

        #region Constructors

        /// <summary> Creates an empty notification object</summary>
        internal Notification()
        {
            dbNotification = new Storage.EntityModels.Notifications();
            this.fields = new Hashtable();
            this.tripId = null;
            this.eventId = null;
        }

        #endregion
        
        #region XMLMethods

        /// <summary>
        /// Builds xml code for representing a field
        /// </summary>
        /// <param name="key">key identifies field</param>
        /// <returns>StringBuilder, the xml code containing key=value, identified by the key</returns>
        private StringBuilder AddXMLField(String key)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<");
            sb.Append(key);
            sb.Append(">");
            sb.Append(fields[key].ToString());
            sb.Append("</");
            sb.Append(key);
            sb.Append(">");
            return sb;
        }

        /// <summary>
        /// Builds xml code representing a template
        /// </summary>
        /// <param name="templateName">template name</param>
        /// <returns>StringBuilder representing xml code generated by a template name</returns>
        private StringBuilder AddXMLTemplate()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<template>");
            sb.Append(this.templateType);
            sb.Append("</template>");
            return sb;
        }

        /// <summary>
        /// Builds xml code representing notification sender
        /// </summary>
        /// <returns>StringBuilder representing xml code generated by notification sender</returns>
        private StringBuilder AddXMLSender()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<sender>");
            sb.Append(this.senderID);
            sb.Append("</sender>");
            return sb;
        }

        /// <summary>
        /// Builds xml code representing this notification
        /// </summary>
        /// <returns>xml code representing this notification</returns>
        private String serialize()
        {
            StringBuilder sb = new StringBuilder();
            //root element named notification
            sb.Append("<notification>");
            //appends template name
            //sb.Append(this.AddXMLTemplate());
            //append fields
            foreach(String k in fields.Keys)
            {
                sb.Append(this.AddXMLField(k));
            }
            //closes root element
            sb.Append("</notification>");
            return sb.ToString();
        }

        /// <summary>
        /// Inserts a new field in notification
        /// </summary>
        /// <param name="key">field name</param>
        /// <param name="value">field value</param>
        public void AddField(String key, Object value)
        {
            fields[key] = value;
        }
        
        /// <summary>
        /// Deserializes xml string into a notification
        /// </summary>
        /// <param name="xml">xml string to be deserialized</param>
       internal void XmlToNotification(String xml)
       {
            //Loads xml string into xml object
            XmlDocument xdoc = new XmlDocument();
            xdoc.LoadXml(xml);
            
            if (xdoc.HasChildNodes)
            {
                //creates a fields hashtable
                fields = new Hashtable(xdoc.ChildNodes.Count);
                //extracts element tagged as notification (root element)
                XmlNode node = xdoc.GetElementsByTagName("notification")[0];
                
                if (node.HasChildNodes)
                {
                    foreach (XmlElement sonNode in node.ChildNodes)
                    {
                        if (sonNode.HasChildNodes)
                            fields[sonNode.Name] = sonNode.FirstChild.Value;
                        else
                            fields[sonNode.Name] = "";
                    }
                }
            }
        }

        #endregion

        #region DB

        /// <summary>
        /// Stores a notification in db
        /// </summary>
        /// <param name="receivers">receiver list</param>
        public void Store(List<int> receivers)
        {
            // no receivers, no need to store
            if (receivers.Count == 0) return;

            // Creates a message and sets fields.
            Storage.EntityModels.Message dbmessage = new Storage.EntityModels.Message();

            if (tripId != null) 
                dbmessage.TripId = tripId;
            
            if (eventId != null) 
                dbmessage.EventId = eventId;
            
            dbmessage.Priority = true;
            dbmessage.Title = "";
            //This field is notification's xml serialization
            dbmessage.Text = this.serialize();
            dbmessage.Date = DateTime.Now;
            dbmessage.Type = this.templateType;

            Storage.EntityModels.TripLoaDataContext context = Storage.StorageManager.Instance.Entities;
            context.Messages.InsertOnSubmit(dbmessage);
            context.SubmitChanges();
            for (int i = 0; i < receivers.Count; i++)
            {
                // For each receiver creates a notification.
                Storage.EntityModels.Notifications dbnotification = new Storage.EntityModels.Notifications();
                dbnotification.MessageId = dbmessage.Id;
                dbnotification.IsRead = false;
                dbnotification.ReceiverUserId = receivers[i];
                dbnotification.UserId = senderID;
                context.Notifications.InsertOnSubmit(dbnotification);
            }
            context.SubmitChanges();
        }

        public void Store(int receiver)
        {
            List<int> user = new List<int>();
            user.Add(receiver);
            this.Store(user);
        }
        
        /// <summary>
        /// Deletes this notification from DB
        /// </summary>
        /// <returns>true if notification exist and is deleted from db. false if it doesn't exist</returns>
        public bool Delete()
        {
            using (TransactionScope scope = new TransactionScope())
            {
                Storage.EntityModels.Notifications notify = null;
                try
                {
                    notify = (from Storage.EntityModels.Notifications n
                              in Storage.StorageManager.Instance.Entities.Notifications
                              where n.Id == this.id
                              select n).First();
                }
                catch (ArgumentNullException) { return false; }
                catch (InvalidOperationException) { return false; }
                
                if ((from Storage.EntityModels.Notifications others
                             in Storage.StorageManager.Instance.Entities.Notifications
                     where others.MessageId == notify.MessageId && others.Id != notify.Id
                     select others).Count() == 0)
                    Storage.StorageManager.Instance.Entities.Messages.DeleteOnSubmit(notify.Message);

                Storage.StorageManager.Instance.Entities.Notifications.DeleteOnSubmit(notify);
                Storage.StorageManager.Instance.Entities.SubmitChanges();
                scope.Complete();
                return true;
            }
        }

        /// <summary>
        /// Mark this notification as read in DB
        /// </summary>
        public void MarkAsRead()
        {
            using (TransactionScope scope = new TransactionScope())
            {
                this.IsRead = true;
                Storage.EntityModels.Notifications notify = null;
                try
                {
                    notify = (from Storage.EntityModels.Notifications n
                                  in Storage.StorageManager.Instance.Entities.Notifications
                              where n.Id == this.id
                              select n).First();
                }
                catch (ArgumentNullException) {}
                catch (InvalidOperationException) {}

                if (notify != null)
                {
                    notify.IsRead = true;
                    Storage.StorageManager.Instance.Entities.SubmitChanges();
                   
                }
                scope.Complete();
            }
        }

        #endregion
    }
}
